#include "SocketSimpleDemo.h"
#include <iostream>

using namespace std;

void socketSimpleTcpServer()
{
    cout << "案例一：最简服务器（流式tcp）" << endl;

#ifdef _WIN32  
    // windows 下需要装载网络访问库
    WSADATA lpWSAData;
    WSAStartup(MAKEWORD(2, 2), &lpWSAData);
#endif

    // 1. 创建 socket
    // SOCK_DGRAM 报式传输，不需要监听可随时发送随时接受，但是报式不稳定，传输效率很高。
    // SOCK_STREAM 流式传输，需要监听，并且会完成三步握手和四步回收，流式传输效率不如报式，但相对稳定。
    // 流式默认使用 tcp 协议，报式默认使用 udp 协议。填写 0 则表示使用默认协议。这里除了 tcp 协议和 udp 协议还有别的协议，如何关联还需要其他研究。
    // 但目前如果使用 tcp 协议来创建报式传输模式会创建失败，同理用 udp 来创建流式传输模式也会失败。
    // 但在一些资料上显示报式模式和流式模式与具体协议无关，udp 也可以用于流式，这显然有些不正确，因为本人亲测通显示指定 IPPROTO_UDP 来创建 SOCK_STREAM。或者 IPPROTO_TCP 来创建 SOCK_DGRAM 都报错失败了。
    // 错误示例：socket(AF_INET, SOCK_STREAM, IPPROTO_UDP) 或 socket(AF_INET, SOCK_DGRAM, IPPROTO_TCP)
    // 正确示例：socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) 或 socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
    SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    if(serverSocket == -1) {
        cerr << "创建失败！！" << endl;
        return;
    }

    // 2. bind
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    // INADDR_NONE：        表示无效的IP地址，用于表示错误或无效的地址。
    // INADDR_BROADCAST：   表示广播地址，用于向网络中的所有主机发送数据。
    // INADDR_LOOPBACK：    表示回环地址，用于将数据发送到本地主机上的回环接口。
    // INADDR_ANY：         表示通配地址，用于指示可以接受来自任何网络接口的数据。
    serverAddr.sin_addr.s_addr = INADDR_ANY;  // 使用 INADDR_ANY 等效于 inet_addr("127.0.0.1"); 在绑定时会自动寻找网卡的IP地址予以绑定，INADDR_ANY 值为 0 即，0.0.0.0 IP
    serverAddr.sin_port = htons(9999);        // 端口使用网络字节序，即大端字节序，函数名称记忆理解：h -> host（主机）; n -> net(网络); s-> short(短整型)。 主机to网络的短整型(htons)
    int bindRet = ::bind(serverSocket, (sockaddr*) &serverAddr, sizeof(serverAddr));
    if(bindRet == -1) {
        cerr << "绑定端口失败！！！" << endl;
        compatCloseSocket(serverSocket);
        return;
    }

    // 3. listen
    int listenRet = listen(serverSocket, 1);   // 要监听的 socket，一次可以获取多少个终端链接
    if(listenRet == -1) {
        cerr << "监听失败！！！" << endl;
        compatCloseSocket(serverSocket);
        return;
    }

    // 4. 阻塞等待链接
    sockaddr_in clientAddr;
    // unsigned int clientAddrLength = sizeof(clientAddr);
    addrlen clientAddrLength = sizeof(clientAddr);
    SOCKET clientSocket = accept(serverSocket, (sockaddr*) &clientAddr, &clientAddrLength);
    if(clientSocket == -1) {
        cerr << "接受客户端异常！！！" << endl;
        compatCloseSocket(serverSocket);
        return;
    }
    // 输出接入链接的客户端信息
    // char ip[24] = { 0 };
    // inet_ntop(AF_INET, &clientAddr.sin_addr.s_addr, ip, sizeof(ip)); // inet_ntop 为 linux 下的接口方法，windows 并不支持。 

    // inet_ntoa 函数 windows和linux都支持，但只能作用于IPV4，无法处理IPV6
    cout << "客户端IP：" << inet_ntoa(clientAddr.sin_addr) << "；端口：" << ntohs(clientAddr.sin_port) << "已连接" << endl;

    // 5. 读取数据
    char readBuff[1024];
    while(true) {
        // memset(readBuff, 0, sizeof(readBuff));
        // int readRet = read(clientSocket, readBuff, sizeof(readBuff));  // linux 下存在的方法。
        int readRet = recv(clientSocket, readBuff, 1024, 0);            // linux 和 windows 都支持
        if(readRet > 0) {
            // 成功读取数据, 返回值表示为接受数据的长度
            cout << "收到客户端数据: " << readBuff << endl;
            // write(clientSocket, readBuff, readRet);                  // linux 下存在的方法。
            send(clientSocket, readBuff, readRet, 0);                   // linux 和 windows 都支持
        } else if (readRet == 0) {
            // 客户端链接已断开
            cout << "客户端断开链接！" << endl;
            break;
        } else {
            // 读取失败，异常
            cout << "读取失败异常: " << endl;
            break;
        }
    }

    // 6. 关闭链接
    compatCloseSocket(clientSocket);
    compatCloseSocket(serverSocket);
    
#ifdef _WIN32  
    // windows 平台下结束了还需要移除网络库的装载
    WSACleanup();
#endif
}

void socketSimpleTcpClient()
{
    cout << "案例一：最简客户端（流式tcp）" << endl;
#ifdef _WIN32  
    // windows 下需要装载网络访问库
    WSADATA lpWSAData;
    WSAStartup(MAKEWORD(2, 2), &lpWSAData);
#endif

    // 1.创建 socket
    SOCKET sendSocket = socket(AF_INET, SOCK_STREAM, 0);
    if(sendSocket == -1) {
        cout << "创建失败！" << endl;
        return;
    }

    // 2.链接服务器
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    // inet_pton(AF_INET, "127.0.0.1", &(serverAddr.sin_addr.s_addr));
    // serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serverAddr.sin_port = htons(9999);     // 网络字节序（大端字节序）
    int connectRet = connect(sendSocket, (sockaddr*) &serverAddr, sizeof(serverAddr));
    if(connectRet == -1) {
        cout << "链接失败！" << endl;
        compatCloseSocket(sendSocket);
        return;
    }

    // 3. 开始和服务端通信
    int count = 10;
    while(count-- > 0) {
        // 发送数据
        char readBuff[1024];
        sprintf(readBuff, "hello server %d", count);
        cout << "客户端发送: " << readBuff << " sizeof::" << sizeof(readBuff) << "  strlen::" << strlen(readBuff) << "  " << sizeof(10) << endl;
        // strlen 这是 C 和 C++ 标准库中的一个函数，用于计算字符串的长度。它接受一个字符串（通常是一个字符数组）作为参数，并返回字符串中字符的数量，不包括终止字符 '\0'。因此，这里 +1 把 '\0' 包含进去。
        // 与 sizeof 有区别，strlen 是从第一个字符开始到接触第一个 '\0' 为止的长度。而 sizeof 是整个字符串数组的内存占用长度，它们都是一字符 char 为单位。char 占用 8 位，
        // 所以当我们 sizeof(10) 返回 4，因为 int 占 32 位，4个 8 位的字节。
        // sizeof 这是一个运算符，用于获取数据类型或对象的大小（以字节为单位）。它可以用于任何数据类型或对象，包括数组、结构体、函数等。sizeof 返回的是类型或对象的大小，而不是长度。
        // write(sendSocket, readBuff, strlen(readBuff) + 1);           // linux 下存在的方法。
        send(sendSocket, readBuff, strlen(readBuff) + 1, 0);            // linux 和 windows 都支持
        
        // 接受数据
        memset(readBuff, 0, sizeof(readBuff));
        // int readRet = read(sendSocket, readBuff, sizeof(readBuff));  // linux 下存在的方法。
        int readRet = recv(sendSocket, readBuff, sizeof(readBuff), 0);  // linux 和 windows 都支持
        if(readRet > 0) {
            // 读取成功
            cout << "服务器回复: " << readBuff << endl;
        } else if (readRet == 0) {
            // 断开链接
            cout << "服务器断开链接！" << endl;
            break;
        } else {
            // 读取失败
            cout << "读取失败异常: " << endl;
            break;
        }
        sleep(1);
    }

    // 4. 关闭链接
    compatCloseSocket(sendSocket);

#ifdef _WIN32  
    // windows 平台下结束了还需要移除网络库的装载
    WSACleanup();
#endif
}